Skip to content

Fix: CT parameter name uniqueness in CancellationToken overload generation#339

Merged
sergey-tihon merged 2 commits intorepo-assist/feat-cancellationtoken-issue-212-2026-03-23-448c1cde90ec7d95from
copilot/sub-pr-336
Mar 25, 2026
Merged

Fix: CT parameter name uniqueness in CancellationToken overload generation#339
sergey-tihon merged 2 commits intorepo-assist/feat-cancellationtoken-issue-212-2026-03-23-448c1cde90ec7d95from
copilot/sub-pr-336

Conversation

Copy link
Contributor

Copilot AI commented Mar 23, 2026

If an OpenAPI spec defines a parameter named cancellationToken, the generated overload would emit two parameters with the same name — one from the spec and one for the injected CancellationToken.

Changes

  • Track used parameter names: The parameter-fold now returns the usedNames set alongside providedParameters instead of discarding it with |> snd
  • Unique CT parameter naming: The injected CT parameter resolves to the first unused name in the sequence cancellationTokencancellationToken1cancellationToken2 → …, matching the existing UniqueNameGenerator convention
  • Position-based extraction unaffected: invokeCode already extracts the CT argument by position (last in args), so it correctly handles the CT regardless of the name chosen
let usedNames, providedParameters =
    ((Set.empty, []), orderedParameters)
    ||> List.fold(fun (names, parameters) current -> ...)
    |> fun (names, ps) -> names, List.rev ps

let ctParamName =
    Seq.initInfinite(fun i -> if i = 0 then "cancellationToken" else $"cancellationToken{i}")
    |> Seq.find(fun n -> not(Set.contains n usedNames))

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…I params named 'cancellationToken'

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/7d588ec7-c4df-4a6c-89f8-9c13c2472d29
Copilot AI changed the title [WIP] [WIP] Address feedback on CancellationToken support for OpenApiClientProvider Fix: CT parameter name uniqueness in CancellationToken overload generation Mar 23, 2026
Copilot AI requested a review from sergey-tihon March 23, 2026 22:26
@sergey-tihon sergey-tihon marked this pull request as ready for review March 25, 2026 07:06
Copilot AI review requested due to automatic review settings March 25, 2026 07:06
@sergey-tihon sergey-tihon merged commit ee4db30 into repo-assist/feat-cancellationtoken-issue-212-2026-03-23-448c1cde90ec7d95 Mar 25, 2026
8 checks passed
@sergey-tihon sergey-tihon deleted the copilot/sub-pr-336 branch March 25, 2026 07:06
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a codegen edge case in the v3 OpenAPI client type provider where injecting a CancellationToken overload could produce duplicate parameter names when the OpenAPI spec already defines a cancellationToken parameter.

Changes:

  • Preserve the usedNames set from the OpenAPI parameter fold instead of discarding it.
  • Generate a unique injected CT parameter name (cancellationToken, cancellationToken1, cancellationToken2, …) that avoids collisions with OpenAPI parameters.
  • Keep CancellationToken extraction position-based in invokeCode (so behavior doesn’t depend on the chosen parameter name).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +180 to 191
// Find a unique name for the CancellationToken parameter that doesn't
// conflict with any OpenAPI parameter already in use.
let ctParamName =
Seq.initInfinite(fun i ->
if i = 0 then
"cancellationToken"
else
$"cancellationToken{i}")
|> Seq.find(fun n -> not(Set.contains n usedNames))

let ctParam = ProvidedParameter(ctParamName, typeof<Threading.CancellationToken>)
providedParameters @ [ ctParam ]
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a regression test for the name-collision scenario this change fixes (an OpenAPI parameter named cancellationToken). Without a test that loads/generates a client from such a spec and asserts the generated overload compiles (and/or that the injected CT param gets a suffixed name), this could regress unnoticed.

Copilot uses AI. Check for mistakes.
Comment on lines +181 to 189
// conflict with any OpenAPI parameter already in use.
let ctParamName =
Seq.initInfinite(fun i ->
if i = 0 then
"cancellationToken"
else
$"cancellationToken{i}")
|> Seq.find(fun n -> not(Set.contains n usedNames))

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a second ad-hoc unique-name generator (Seq.initInfinite + Set.contains) even though the codebase already has UniqueNameGenerator with the same suffixing convention (and case-insensitive uniqueness). Consider factoring this into a shared helper (or reusing UniqueNameGenerator by seeding it with usedNames) to avoid future divergence in how “unique names” are defined.

Suggested change
// conflict with any OpenAPI parameter already in use.
let ctParamName =
Seq.initInfinite(fun i ->
if i = 0 then
"cancellationToken"
else
$"cancellationToken{i}")
|> Seq.find(fun n -> not(Set.contains n usedNames))
// conflict with any OpenAPI parameter already in use, using the shared
// UniqueNameGenerator to keep suffixing and case sensitivity consistent.
let nameGen = UniqueNameGenerator()
usedNames |> Set.iter (fun n -> nameGen.Claim n |> ignore)
let ctParamName = nameGen.Claim "cancellationToken"

Copilot uses AI. Check for mistakes.
Copilot AI added a commit that referenced this pull request Mar 25, 2026
…dering + uniqueness improvements from this PR

Both branches fixed CT name uniqueness independently. This branch additionally
fixes the parameter ordering bug (CT must precede optional params). The merge
keeps the more complete implementation (buildProvidedParameters helper, two-pass
required/optional split, ctArgIndex insertion) and discards the simpler
base-branch approach that still appended CT after optional params.
sergey-tihon added a commit that referenced this pull request Mar 25, 2026
…vider generated methods (closes #212) (#336)

* feat: add CancellationToken support to OpenApiClientProvider generated methods (closes #212)

- Add CallAsync overload with CancellationToken to ProvidedApiClientBase
- Thread CancellationToken from generated methods through to HttpClient.SendAsync
- Each generated method gains an optional cancellationToken parameter (defaults to CancellationToken.None)
- Backward-compatible: existing call sites without CT continue to work unchanged
- Add unit tests: success with CancellationToken.None, cancellation propagation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: trigger checks

* fix: replace optional struct CancellationToken parameter with method overloads (#337)

* Initial plan

* Fix: revert global.json and address CancellationToken build failures

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/1861c3cb-6a0a-438a-aa31-f65b8c809f88

* fix: use method overloading for CancellationToken support instead of optional struct parameter

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/1861c3cb-6a0a-438a-aa31-f65b8c809f88

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>

* Add type provider integration tests for CancellationToken-overloaded methods (#338)

* Fix: CT parameter name uniqueness in CancellationToken overload generation (#339)

* Initial plan

* fix: generate unique CT parameter name to avoid collision with OpenAPI params named 'cancellationToken'

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/7d588ec7-c4df-4a6c-89f8-9c13c2472d29

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>

* Fix CancellationToken parameter ordering and name collision in v3 OperationCompiler (#341)

* Initial plan

* fix: insert CT between required and optional params; generate unique CT name

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/b0c519de-0186-40ca-8174-42ed67a5316a

* fix: add explicit restore + --no-restore to BuildTests to fix NETSDK1005

Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/565d6633-576d-4587-b924-a29b0ea53c2c

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>

* refactor: use UniqueNameGenerator for CT param name uniqueness

Replace hand-coded recursive findUniqueName function with the existing
UniqueNameGenerator utility (already used in DefinitionCompiler and
for method name deduplication in OperationCompiler).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add optional occupiedNames parameter to UniqueNameGenerator constructor

Allows callers to pre-seed the generator with names that are already taken,
so MakeUnique will never return any of those names without a numeric suffix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: single CallAsync overload + single generated method with optional CancellationToken

- Remove no-CT CallAsync overload from ProvidedApiClientBase; keep only the
  version with explicit CancellationToken (quotation code always supplies it)
- Remove double-compilation in OperationCompiler: one method per operation with
  optional cancellationToken (null default = default(CancellationToken).None)
- Update RuntimeHelpersTests to pass CancellationToken.None explicitly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* cleanup: simplify OperationCompiler and add default/async CT tests

- Use List.map instead of List.collect since compileOperation returns a single method
- Clean up comments in OperationCompiler
- Add test for calling generated method without CancellationToken (default token)
- Add test for async (PreferAsync=true) generated method without CancellationToken

* feat: propagate CancellationToken through ReadAsStringAsync/ReadAsStreamAsync via RuntimeHelpers

Add readContentAsString and readContentAsStream wrappers to RuntimeHelpers
with #if NET5_0_OR_GREATER guards, enabling CancellationToken propagation
in generated quotation code that must compile against netstandard2.0.
Also add explicit CancellationToken integration tests and conditional
CT support in ProvidedApiClientBase error path.

* test: add CT coverage for stream, text/plain, async cancellation, and async POST paths

* fix: async tests

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Sergey Tihon <sergey.tihon@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants